package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ApArticleConfigMapper;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.article.service.ArticleFreemarkerService;
import com.heima.article.service.ArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.common.exception.CustomException;
import com.heima.common.redis.CacheService;
import com.heima.model.article.dtos.ArticleDto;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.article.mess.ArticleVisitStreamMess;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleConfig;
import com.heima.model.article.pojos.ApArticleContent;
import com.heima.model.article.vos.HotArticleVo;
import com.heima.model.comment.pojos.CommentSaveDto;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.vos.SearchArticleVo;
import com.heima.model.wemedia.dtos.WmCommentDto;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.vos.WmArticleVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.nntp.Article;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Wrapper;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Transactional
@Slf4j
public class ArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ArticleService {

    @Autowired
    private ApArticleMapper apArticleMapper;
    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private ArticleFreemarkerService articleFreemarkerService;
    @Autowired
    private CacheService cacheService;

    /**
     * 加载首页 1： 加载更多，首页 2：加载最新
     *
     * @param dto
     * @param type
     * @return
     */
    public ResponseResult load(ArticleHomeDto dto, Short type) {
        //1、参数校验
        if (dto == null) {
            throw new CustomException(AppHttpCodeEnum.PARAM_REQUIRE);
        }
        //2、数据处理
        //2.1、如果前端 size没传，给个默认值 10；size过大（超过50），也改为50
        if (dto.getSize() == null || dto.getSize() < 0) dto.setSize(10);
        dto.setSize(Math.min(50, dto.getSize()));
        //2.2、如果tag没给，设为默认频道，"__all__"
        if (StringUtils.isBlank(dto.getTag())) dto.setTag(ArticleConstants.DEFAULT_TAG);
        //2.3、如果前端没传最大时间或者最小时间，设为当前时间
        if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
        //2.4 type为null 默认加载首页
        if (type == null) type = ArticleConstants.LOADTYPE_LOAD_MORE;

        //3、业务操作
        List<ApArticle> list = apArticleMapper.loadArticleList(dto, type);
        //4、返回结果
        return ResponseResult.okResult(list);
    }

    /**
     * 保存app端文章
     *
     * @param dto
     * @return
     */
    public ResponseResult saveArticle(ArticleDto dto) {
        //0、校验参数
        if (dto == null) {
            throw new CustomException(AppHttpCodeEnum.PARAM_INVALID);
        }
        ApArticle apArticle = new ApArticle();
        //拷贝属性
        BeanUtils.copyProperties(dto, apArticle);
        //1、根据id判断是新增还是修改
        if (dto.getId() == null) {
            //2、新增
            //2.1、保存文章
            save(apArticle);
            //2.2、保存文章配置
            apArticleConfigMapper.insert(new ApArticleConfig(apArticle.getId()));
            //2.3、保存文章内容
            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setContent(dto.getContent());
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContentMapper.insert(apArticleContent);
        } else {
            //3、修改
            //3.1、修改文章
            updateById(apArticle);
            //3.2、先查出文章内容，再修改文章内容 再存入
            ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));
            apArticleContent.setContent(dto.getContent());
            apArticleContentMapper.updateById(apArticleContent);
//            apArticleContentMapper.update(null,Wrappers
//                            .<ApArticleContent>lambdaUpdate()
//                            .set(ApArticleContent::getContent, dto.getContent())
//                            .eq(ApArticleContent::getArticleId, dto.getId()));
        }

        //4、异步调用，基于freemarker生成静态html文件上传到minIO中
        articleFreemarkerService.buildArticleToMinIO(apArticle, dto.getContent());

        //5、返回文章id
        return ResponseResult.okResult(apArticle.getId());
    }

    /**
     * 上架文章 保存es
     *
     * @param articleId
     * @return
     */
    //todo 上架文章 es
    @Override
    public ResponseResult getArticleToES(Long articleId) {
        // 根据文章id先得到文章
        ApArticle apArticle = getById(articleId);
        // 根据文章id得到文章内容
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, articleId));
        // 组装searchArticleVo返回
        SearchArticleVo searchArticleVo = new SearchArticleVo();
        // 绝大部分属性article里面都有
        BeanUtils.copyProperties(apArticle, searchArticleVo);
        // 文章内容
        searchArticleVo.setContent(apArticleContent.getContent());
        // 返回
        return ResponseResult.okResult(searchArticleVo);
    }


    /**
     * 首页加载热点文章
     *
     * @param dto
     * @param type
     * @return
     */
    @Override
    public ResponseResult load2(ArticleHomeDto dto, Short type) {
        //1、先从缓存中读取数据
        String hotArticleJson = cacheService.get(ArticleConstants.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
        //2、非空判断
        if (StringUtils.isNotBlank(hotArticleJson)) {
            //3、转成原来对象 也可以直接用parseObject
            List<HotArticleVo> hotArticleVos = JSON.parseArray(hotArticleJson, HotArticleVo.class);
            //4、返回响应结果
            return ResponseResult.okResult(hotArticleVos);
        } else // 缓存中没有再从数据库读取
            return load(dto, type);
    }

    /**
     * 更新文章的分值，同时更新缓存中的热点文章数据
     *
     * @param mess
     */
    @Override
    public void updateScore(ArticleVisitStreamMess mess) {
        //0、先计算得到文章之前的分数
        ApArticle oldArticle = getById(mess.getArticleId());
        int oldScore = computeScore(oldArticle);
        //1、更新文章数据的点赞 评论 阅读 收藏
        ApArticle newArticle = updateArticle(mess);
        //1.1、计算修改后的分数 仅仅是刚才十秒内的新分数
        newArticle.setCollection(mess.getCollect());
        newArticle.setComment(mess.getComment());
        newArticle.setLikes(mess.getLike());
        newArticle.setViews(mess.getView());
        int newScore = computeScore(newArticle);
        //十秒内分数权重 * 3
        newScore *= 3;
        //现在的总分
        int score = oldScore + newScore;
        //2、更新文章对映频道的热点文章
        replaceDataToRedis(newArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + oldArticle.getChannelId());

        //4、更新推荐对应的热点文章
        replaceDataToRedis(newArticle, score, ArticleConstants.HOT_ARTICLE_FIRST_PAGE + ArticleConstants.DEFAULT_TAG);

    }

    /**
     * 替换数据到redis
     *
     * @param newArticle
     * @param score
     */
    private void replaceDataToRedis(ApArticle newArticle, int score, String key) {
        //2.1、从redis中得出原来热点数据
        String articleListStr = cacheService.get(key);
        if (StringUtils.isNotBlank(articleListStr)) {
            //2.2、反序列化成原来对象集合
            List<HotArticleVo> hotArticleVos = JSON.parseArray(articleListStr, HotArticleVo.class);
            //信号位
            boolean flag = true;
            //2.3、如果热点文章已经有该文章，则只是改变其分数
            for (HotArticleVo hotArticleVo : hotArticleVos) {
                //若匹配到，
                if (hotArticleVo.getChannelId().equals(newArticle.getChannelId())) {
                    //修改分数
                    hotArticleVo.setScore(score);
                    //改变信号位
                    flag = false;
                    //结束循环
                    break;
                }
            }
            /**
             * 判断信号位
             * true： 热点文章里面没有该文章，则需要再判断热点文章是否已经30个了
             *                                              已经30个 比较最后一个的分数与这篇文章的分数 小于则替换
             *                                              没到30个 直接添加
             * false：直接跳过
             */
            if (flag) {
                if (hotArticleVos.size() >= 30) {
                    //热点文章按照分数倒序排序
                    hotArticleVos = hotArticleVos.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
                    //得到最后一个
                    HotArticleVo lastHotVo = hotArticleVos.get(hotArticleVos.size() - 1);
                    //判断最后一名分数与新晋佼佼者
                    if (lastHotVo.getScore() < score) {
                        //移除原来的
                        hotArticleVos.remove(hotArticleVos.size() - 1);
                        //创建新的
                        HotArticleVo hotArticleVo = new HotArticleVo();
                        //拷贝属性
                        BeanUtils.copyProperties(newArticle, hotArticleVo);
                        //完善属性
                        hotArticleVo.setScore(score);
                        //添加新的
                        hotArticleVos.add(hotArticleVo);
                    }
                } else {
                    //没到30个 直接添加
                    //创建新的
                    HotArticleVo hotArticleVo = new HotArticleVo();
                    //拷贝属性
                    BeanUtils.copyProperties(newArticle, hotArticleVo);
                    //完善属性
                    hotArticleVo.setScore(score);
                    //添加新的
                    hotArticleVos.add(hotArticleVo);
                }
            }
            //重新排序后
            hotArticleVos = hotArticleVos.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
            //加入redis
            cacheService.set(key, JSON.toJSONString(hotArticleVos));
        }
    }

    /**
     * 计算文章分数
     *
     * @param apArticle
     * @return
     */
    private int computeScore(ApArticle apArticle) {
        int score = 0;
        //点赞
        if (apArticle.getLikes() != null) {
            score += apArticle.getLikes() * ArticleConstants.HOT_ARTICLE_LIKE_WEIGHT;
        }
        //阅读
        if (apArticle.getViews() != null) {
            score += apArticle.getViews();
        }
        //评论
        if (apArticle.getComment() != null) {
            score += apArticle.getComment() * ArticleConstants.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        //收藏
        if (apArticle.getCollection() != null) {
            score += apArticle.getCollection() * ArticleConstants.HOT_ARTICLE_COLLECTION_WEIGHT;
        }
        return score;
    }

    /**
     * 更新文章数据的点赞 评论 阅读 收藏
     *
     * @param mess
     */
    private ApArticle updateArticle(ArticleVisitStreamMess mess) {
        //1、先根据文章id查询文章信息
        ApArticle apArticle = getById(mess.getArticleId());
        if (apArticle == null) {
            throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        //2、补充属性
        apArticle.setViews(apArticle.getViews() == null ? mess.getView() : apArticle.getViews() + mess.getView());
        apArticle.setCollection(apArticle.getCollection() == null ? mess.getCollect() : apArticle.getCollection() + mess.getCollect());
        apArticle.setComment(apArticle.getComment() == null ? mess.getComment() : apArticle.getComment() + mess.getComment());
        apArticle.setLikes(apArticle.getLikes() == null ? mess.getLike() : apArticle.getLikes() + mess.getLike());
        //3、修改文章
        updateById(apArticle);
        //4、返回文章
        return apArticle;
    }

    /**
     * 通过文章id 得到作者信息
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult getAuthorInfo(CommentSaveDto dto) {
        //1、通过里面的文章id，得到作者信息
        ApArticle apArticle = getById(dto.getArticleId());
        //2、非空返回
        if (apArticle == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "文章不存在");
        }
        return ResponseResult.okResult(apArticle);
    }

    /**
     * 一段时间文章信息的统计
     *
     * @param dto
     * @return
     */
    @Override
    public ResponseResult newsDimension(WmCommentDto dto) {
        LambdaQueryWrapper<ApArticle> lqw = getLqw(dto);
            //不分页操作
            List<ApArticle> articles = list(lqw);
            if (articles == null || articles.size() == 0) {
                throw new CustomException(AppHttpCodeEnum.DATA_NOT_EXIST);
            }
            //图文发布量
            int count = articles.size();
            //点赞数量 收藏数量
            int likes = 0, collect = 0;
            for (ApArticle article : articles) {
                //点赞
                if (article.getLikes() != null) {
                    likes += article.getLikes();
                }
                //收藏
                if (article.getCollection() != null) {
                    collect += article.getCollection();
                }
            }
            //封装对象返回
            WmArticleVo wmArticleVo = WmArticleVo.builder()
                    .collectNum(collect)
                    .likesNum(likes)
                    .publishNum(count)
                    .build();
            return ResponseResult.okResult(wmArticleVo);

    }

    /**
     * lamada表达式
     * @param dto
     * @return
     */
    private LambdaQueryWrapper<ApArticle> getLqw(WmCommentDto dto) {
        //时间格式转换器
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        //准备lamada表达式
        LambdaQueryWrapper<ApArticle> lqw = new LambdaQueryWrapper<>();
        try {
            lqw.between(ApArticle::getPublishTime, sdf.parse(dto.getBeginDate()), sdf.parse(dto.getEndDate()));
            lqw.eq(ApArticle::getAuthorId, dto.getUserId());
            lqw.orderByDesc(ApArticle::getPublishTime);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return lqw;
    }

    /**
     * 分页展示文章列表，展示当前时间范围内的具体文章阅读、评论、收藏的数量。
     *
     * @param dto
     * @return
     */
    @Override
    public PageResponseResult newsPage(WmCommentDto dto) {
        LambdaQueryWrapper<ApArticle> lqw = getLqw(dto);
        //分页操作 分页参数
        IPage<ApArticle> page = new Page(dto.getPage(),dto.getSize());
        //查询
        page(page,lqw);
        //创建返回对象
        PageResponseResult pageResponseResult = new PageResponseResult(dto.getPage(), dto.getSize(), (int) page.getTotal());
        pageResponseResult.setCode(AppHttpCodeEnum.SUCCESS.getCode());
        //数据处理
        for (ApArticle record : page.getRecords()) {
            if (record.getCollection() == null) {
                record.setCollection(0);
            }
            if (record.getComment() == null) {
                record.setComment(0);
            }
            if (record.getLikes() == null) {
                record.setLikes(0);
            }
            if (record.getViews() == null) {
                record.setViews(0);
            }
        }
        pageResponseResult.setData(page.getRecords());
        return pageResponseResult;
    }
}